//
// Created by suzhen on 2019-03-30.
//

#pragma once

#include <hawk/core/thread/endian.hpp>

#include "buffer.h"

namespace qio {

template <typename T>
struct DataDesc {
  static_assert(std::is_integral<T>::value, "must be integral");

  static constexpr std::size_t data_size = sizeof(T);

  static std::array<char, data_size> Extract(T i) QIO_NOEXCEPT {
    std::array<char, data_size> out{};
    const auto *ptr = reinterpret_cast<char *>(&i);
    for (int idx = 0; idx < data_size; ++idx) {
      out[idx] = ptr[likely(hawk::core::thread::IsLittleEndian())
                         ? (data_size - 1 - idx)
                         : idx];
    }
    return out;
  }

  static T Pack(const char *data) QIO_NOEXCEPT {
    using ArrType = std::array<char, data_size>;

    ArrType arr{};

    if (likely(hawk::core::thread::IsLittleEndian())) {
      std::copy_n(data, data_size, arr.rbegin());
    } else {
      std::copy_n(data, data_size, arr.begin());
    }

    return *(T *)arr.data();
  }
};

class ChanBuffer final : public Buffer {
 public:
  ~ChanBuffer() QIO_NOEXCEPT override = default;

  template <typename T, typename std::enable_if<
                            std::is_integral<T>::value>::type * = nullptr>
  T Peek(int offset = 0) const QIO_NOEXCEPT {
    return DataDesc<T>::Pack(data_.data() + offset);
  }

  template <typename T, typename std::enable_if<
                            std::is_integral<T>::value>::type * = nullptr>
  T Read() QIO_NOEXCEPT {
    T ret = Peek<T>();
    Pop(DataDesc<T>::data_size);
    return ret;
  }

  template <typename T, typename std::enable_if<
                            std::is_integral<T>::value>::type * = nullptr>
  void Write(T i) QIO_NOEXCEPT {
    const auto v = DataDesc<T>::Extract(i);
    std::copy_n(v.begin(), v.size(), std::back_inserter(data_));
  }

  void Write(const char *data, std::size_t n) QIO_NOEXCEPT override;

  void Write(const DataType &data) QIO_NOEXCEPT override;

  DataType Read(std::size_t n) QIO_NOEXCEPT override;

  DataType Peek(int offset, std::size_t n) const QIO_NOEXCEPT override;

  std::int8_t PeekInt8(int offset) const QIO_NOEXCEPT override;

  std::uint8_t PeekUInt8(int offset) const QIO_NOEXCEPT override;

  std::int16_t PeekInt16(int offset) const QIO_NOEXCEPT override;

  std::uint16_t PeekUInt16(int offset) const QIO_NOEXCEPT override;

  std::int32_t PeekInt32(int offset) const QIO_NOEXCEPT override;

  std::uint32_t PeekUInt32(int offset) const QIO_NOEXCEPT override;

  std::int8_t ReadInt8() QIO_NOEXCEPT override;

  std::uint8_t ReadUInt8() QIO_NOEXCEPT override;

  std::int16_t ReadInt16() QIO_NOEXCEPT override;

  std::uint16_t ReadUInt16() QIO_NOEXCEPT override;

  std::int32_t ReadInt32() QIO_NOEXCEPT override;

  std::uint32_t ReadUInt32() QIO_NOEXCEPT override;

  DataType ReadAll() QIO_NOEXCEPT override;

  void WriteInt8(std::int8_t i) QIO_NOEXCEPT override;

  void WriteUInt8(std::uint8_t i) QIO_NOEXCEPT override;

  void WriteInt16(std::int16_t i) QIO_NOEXCEPT override;

  void WriteUInt16(std::uint16_t i) QIO_NOEXCEPT override;

  void WriteInt32(std::int32_t i) QIO_NOEXCEPT override;

  void WriteUInt32(std::uint32_t i) QIO_NOEXCEPT override;

  std::size_t GetReadableLength() const QIO_NOEXCEPT override;

  void Pop(std::size_t n) QIO_NOEXCEPT override;

 private:
  Buffer::DataType data_;
};

}  // namespace qio
